﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace WPFCharts
{
    /// <summary>
    /// Class that contains various utilities for drawing charts
    /// </summary>
    public static class ChartUtilities
    {
        /// <summary>
        /// Converts a chart to tab separated values
        /// </summary>
        /// <param name="xyLineChart"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        public static string ConvertChartToSpreadsheetText(WPFLineChart xyLineChart, char token)
        {
            int maxPrimitiveLength = 0;
            foreach (ChartLine primitive in xyLineChart.Primitives)
            {
                maxPrimitiveLength = Math.Max(maxPrimitiveLength, primitive.Points.Count);
            }
            string[] grid = new string[maxPrimitiveLength + 1];
            foreach (ChartLine primitive in xyLineChart.Primitives)
            {
                /*if (primitive.ShowInLegend)
                {
                    int row = 0;
                    grid[row] += primitive.Label + " X" + token + primitive.Label + " Y" + token;
                    foreach (System.Windows.Point point in primitive.Points)
                    {
                        ++row;
                        grid[row] += point.X.ToString() + token + point.Y.ToString() + token;
                    }
                    ++row;
                    while (row < grid.Length)
                    {
                        grid[row] += token.ToString() + token.ToString();
                        ++row;
                    }
                }*/
            }

            StringBuilder sb = new StringBuilder();
            sb.AppendLine(xyLineChart.Title);
            foreach (string line in grid)
            {
                sb.AppendLine(line.Substring(0, line.Length - 1));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Calculates the as near to the input as possible, a power of 10 times 1,2, or 5 
        /// </summary>
        /// <param name="optimalValue"> The value to get closest to</param>
        /// <returns>The nearest value to the input value</returns>
        public static double Closest_1_2_5_Pow10(double optimalValue)
        {
            double[] numbersList = { 1.0, 2.0, 5.0 };
            return ClosestValueInListTimesBaseToInteger(optimalValue, numbersList, 10.0);
        }

        /// <summary>
        /// Calculates the closest possible value to the optimalValue passed
        /// in, that can be obtained by multiplying one of the numbers in the
        /// list by the baseValue to the power of any integer.
        /// </summary>
        /// <param name="optimalValue">The number to get closest to</param>
        /// <param name="numbers">List of numbers to mulitply by</param>
        /// <param name="baseValue">The base value</param>
        /// <returns></returns>
        public static double ClosestValueInListTimesBaseToInteger(double optimalValue, double[] numbers, double baseValue)
        {
            double multiplier = Math.Pow(baseValue, Math.Floor(Math.Log(optimalValue) / Math.Log(baseValue)));
            double minimumDifference = baseValue * baseValue * multiplier;
            double closestValue = 0.0;
            double minimumNumber = baseValue * baseValue;

            foreach (double number in numbers)
            {
                double difference = Math.Abs(optimalValue - number * multiplier);
                if (difference < minimumDifference)
                {
                    minimumDifference = difference;
                    closestValue = number * multiplier;
                }
                if (number < minimumNumber)
                {
                    minimumNumber = number;
                }
            }

            if (Math.Abs(optimalValue - minimumNumber * baseValue * multiplier) < Math.Abs(optimalValue - closestValue))
                closestValue = minimumNumber * baseValue * multiplier;

            return closestValue;
        }

        /// <summary>
        /// Gets the plot rectangle that is required to hold all the
        /// lines in the primitive list
        /// </summary>
        /// <param name="primitiveList"></param>
        /// <returns></returns>
        public static Rect GetPlotRectangle(List<ChartLine> primitiveList)
        {
            return GetPlotRectangle(primitiveList, 0);
        }

        /// <summary>
        /// Gets a nominally oversize rectangle that the plot will be drawn into
        /// </summary>
        /// <param name="primitiveList"></param>
        /// <param name="oversize"></param>
        /// <returns></returns>
        public static Rect GetPlotRectangle(List<ChartLine> primitiveList, double oversize)
        {
            // Get the extent of the plot region by going through
            // all the lines, and finding the min and max points
            bool firstPass = true;
            Vector minPoint = new Vector(0, 0);
            Vector maxPoint = new Vector(0, 0);
            foreach (ChartLine primitive in primitiveList)
            {
                if (!firstPass)
                {
                    minPoint.X = Math.Min(primitive.MinPoint.X, minPoint.X);
                    minPoint.Y = Math.Min(primitive.MinPoint.Y, minPoint.Y);
                    maxPoint.X = Math.Max(primitive.MaxPoint.X, maxPoint.X);
                    maxPoint.Y = Math.Max(primitive.MaxPoint.Y, maxPoint.Y);
                }
                else
                {
                    minPoint.X = primitive.MinPoint.X;
                    maxPoint.X = primitive.MaxPoint.X;
                    minPoint.Y = primitive.MinPoint.Y;
                    maxPoint.Y = primitive.MaxPoint.Y;
                    firstPass = false;
                }
            }

            // Make sure that the plot size is greater than zero
            if (maxPoint.Y == minPoint.Y)
            {
                if (maxPoint.Y != 0)
                {
                    maxPoint.Y *= 1.05;
                    minPoint.Y *= 0.95;
                }
                else
                {
                    maxPoint.Y = 1;
                    minPoint.Y = 0;
                }
            }

            if (maxPoint.X == minPoint.X)
            {
                if (maxPoint.X != 0.0)
                {
                    maxPoint.X *= 1.05;
                    minPoint.X *= 0.95;
                }
                else
                {
                    maxPoint.X = 1;
                    minPoint.X = 0;
                }
            }

            // Add a bit of a border around the plot
            //maxPoint.X = maxPoint.X; //+ 0.1d; //+ Math.Round((maxPoint.X - minPoint.X) * oversize * .5);
            //maxPoint.Y = maxPoint.Y; //+ 0.1d; //+ Math.Round((maxPoint.Y - minPoint.Y) * oversize * .5);
            //minPoint.X = minPoint.X; //- 0.1d; //- Math.Round((maxPoint.X - minPoint.X) * oversize * .5);
            //minPoint.Y = minPoint.Y; //- 0.1d; //- Math.Round((maxPoint.Y - minPoint.Y) * oversize * .5);

            return new Rect(minPoint.X, minPoint.Y, maxPoint.X - minPoint.X, maxPoint.Y - minPoint.Y);
        }//GetPlotRectangle

        /// <summary>
        /// Converts a ChartLine object to a ChartPolygon object that has
        /// one edge along the bottom Horizontal base line in the plot.
        /// </summary>
        /// <param name="chartLine"></param>
        /// <returns></returns>
        public static ChartLine ChartLineToBaseLinedPolygon(ChartLine chartLine)
        {
            ChartLine chartPolygon = chartLine.Clone();

            Point firstPoint = chartPolygon.Points[0];
            firstPoint.Y = 0;
            Point lastPoint = chartPolygon.Points[chartPolygon.Points.Count - 1];
            lastPoint.Y = 0;

            chartPolygon.InsertPoint(firstPoint, 0);
            chartPolygon.AddPoint(lastPoint);

            return chartPolygon;
        }

        /// <summary>
        /// Takes two lines and creates a polyon between them
        /// </summary>
        /// <param name="baseLine"></param>
        /// <param name="topLine"></param>
        /// <returns></returns>
        public static ChartLine LineDiffToPolygon(ChartLine baseLine, ChartLine topLine)
        {
            ChartLine polygon = new ChartLine();
            List<Point> baseLinePoints = baseLine.Points;
            List<Point> topLinePoints = topLine.Points;

            for (int pointNo = baseLinePoints.Count - 1; pointNo >= 0; --pointNo)
            {
                polygon.AddPoint(baseLinePoints[pointNo]);
            }
            for (int pointNo = 0; pointNo < topLinePoints.Count; ++pointNo)
            {
                polygon.AddPoint(topLinePoints[pointNo]);
            }

            return polygon;
        }

        /// <summary>
        /// Creates a 50% hatch patter for filling a polygon
        /// </summary>
        /// <param name="color"></param>
        /// <param name="blockSize"></param>
        /// <returns></returns>
        public static DrawingBrush CreateHatch50(Color color, Size blockSize)
        {
            GeometryGroup group = new GeometryGroup();
            RectangleGeometry rectangle1 = new RectangleGeometry(new Rect(new Point(0, 0), blockSize));
            RectangleGeometry rectangle2 = new RectangleGeometry(new Rect((Point)blockSize, blockSize));
            group.Children.Add(rectangle1);
            group.Children.Add(rectangle2);

            GeometryDrawing drawing = new GeometryDrawing(new SolidColorBrush(color), null, group);

            DrawingBrush brush = new DrawingBrush(drawing);
            brush.TileMode = TileMode.Tile;
            brush.ViewportUnits = BrushMappingMode.Absolute;
            brush.Viewport = new Rect(0, 0, blockSize.Width * 2, blockSize.Height * 2);
            return brush;
        }
    }
}
